﻿namespace LaoLuo
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// 以Task方式按照指定处理方法执行指定数据,包括重试机制,适合处理数量较少数据,如果数据较多,可考虑分组
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    public class TaskExecutor<T> : IDisposable
    {
        /// <summary>
        /// 单个数据的处理次数记录
        /// </summary>
        private Dictionary<T, int> handleTimesCount = new Dictionary<T, int>();

        /// <summary>
        /// 单个数据的处理结果记录
        /// </summary>
        private Dictionary<T, bool> handleStatus = new Dictionary<T, bool>();

        /// <summary>
        /// 单个数据的处理方法
        /// </summary>
        private Func<T, bool> handleMethod;

        /// <summary>
        /// 初始化TaskExecutor&lt;T>的新实例
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="handleMethod">数据处理方法</param>
        /// <param name="handleTimesLimit">最大处理次数</param>
        /// <param name="waitBeforeRetry">重试前等待秒数</param>
        public TaskExecutor(IEnumerable<T> data, Func<T, bool> handleMethod, int handleTimesLimit = 3, int waitBeforeRetry = 0)
        {
            this.handleMethod = handleMethod;
            this.HandleTimesLimit = handleTimesLimit;
            this.WaitBeforeRetry = waitBeforeRetry;

            foreach (var item in data)
            {
                this.handleTimesCount.Add(item, 0);
                this.handleStatus.Add(item, false);
            }
        }

        /// <summary>
        /// 初始化TaskExecutor&lt;T>的新实例
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="handleMethod">数据处理方法</param>
        /// <param name="handleTimesLimit">最大处理次数</param>
        /// <param name="waitBeforeRetry">重试前等待秒数</param>
        public TaskExecutor(T data, Func<T, bool> handleMethod, int handleTimesLimit = 3, int waitBeforeRetry = 0)
        {
            this.handleMethod = handleMethod;
            this.HandleTimesLimit = handleTimesLimit;
            this.WaitBeforeRetry = waitBeforeRetry;

            this.handleTimesCount.Add(data, 0);
            this.handleStatus.Add(data, false);
        }

        /// <summary>
        /// 重试前等待秒数,默认不等待
        /// </summary>
        public int WaitBeforeRetry { get; set; }

        /// <summary>
        /// 失败会重试,总请求次数不超过此值
        /// </summary>
        public int HandleTimesLimit { get; private set; }

        /// <summary>
        /// 开始处理
        /// </summary>
        /// <returns>处理失败的数据</returns>
        public IEnumerable<T> Handle()
        {
            while (true)
            {
                var toSend = this.GetToHandle().ToList();

                if (toSend.Count == 0)
                {
                    break;
                }

                var tasks = new Task[toSend.Count];

                for (var i = 0; i < toSend.Count; i++)
                {
                    var s = toSend[i];
                    tasks[i] = Task.Factory.StartNew(() =>
                    {
                        if (IsSleepy(s))
                        {
#if DEBUG
                            Console.WriteLine("go to sleep for {0} sec", this.WaitBeforeRetry);
#endif
                            Thread.Sleep(this.WaitBeforeRetry * 1000);

#if DEBUG
                            Console.WriteLine("wake up after {0} sec", this.WaitBeforeRetry);
#endif
                        }

                        handleStatus[s] = this.handleMethod(s);
                        handleTimesCount[s]++;
                    });
                }

                Task.WaitAll(tasks);
            }

            return this.handleStatus.Where(i => !i.Value).Select(i => i.Key);
        }

        /// <summary>
        /// dispose
        /// </summary>
        public void Dispose()
        {
            this.handleMethod = null;
            this.handleStatus = null;
            this.handleTimesCount = null;
        }

        /// <summary>
        /// 获取需要处理的数据,筛选未成功且未达到处理次数限制的
        /// </summary>
        /// <returns>需要处理的数据</returns>
        private IEnumerable<T> GetToHandle()
        {
            var faulted = this.handleStatus.Where(i => !i.Value).Select(i => i.Key);
            var inLimited = this.handleTimesCount.Where(i => i.Value < this.HandleTimesLimit).Select(i => i.Key);

            return faulted.Intersect(inLimited);
        }

        /// <summary>
        /// 判断数据是否需要休眠一定时间后再处理
        /// </summary>
        /// <param name="s">数据</param>
        /// <returns>如果设置的休眠时间大于0且是重试的数据,返回true,否则返回false</returns>
        private bool IsSleepy(T s)
        {
            return this.WaitBeforeRetry > 0 && this.handleTimesCount[s] > 0;
        }
    }
}
